#!/usr/bin/env python

# jsprocessor
#
# http://code.google.com/p/js-ria-tools/
# 
# This script reads a JavaScript file and processes any directives that are
# specified in the file.
# 
# Written by Steven Kollars
# 
# Copyright (c) 2008 Rackspace (www.rackspace.com)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy 
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights 
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
# copies of the Software, and to permit persons to whom the Software is 
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in 
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#

import os
import sys
import getopt

class JSProcessor(object):
    def __init__(self, source, destination):
        self.source = source
        self.destination = destination
        
        self.in_class = False
        self.namespaces = []
        self.current_ns = None
        self.current_alias = None
        self.callback_count = 0
    
    def process(self):
        try:
            sf = open(self.source)
            try:
                lines = sf.readlines()
            finally:
                sf.close()
                
        except IOError:
            print "Couldn't read source file"
            return
            
        pl = []
        
        for line in lines:
            stripped_line = line.strip()
            if stripped_line.startswith('#'): # Start all directives with #
                tokens = stripped_line[1:].split(" ")
                cmd = tokens[0]
                tokens = tokens[1:]
                
                if cmd == 'class':
                    pl.append(self.handle_class(tokens))
                    
                elif cmd == 'cls':
                    pl.append(self.handle_cls())
                    
                elif cmd == 'endclass':
                    pl.append(self.handle_endclass(tokens))
                    
                elif cmd == 'ns':
                    pl.append(self.handle_ns(tokens))
                    
                elif cmd == 'endns':
                    pl.append(self.handle_endns(tokens))
                    
                elif cmd == 'waitfor':
                    pl.append(self.handle_waitfor(tokens))
                    
            else:
                pl.append(line)
                
        df = open(self.destination, 'w')
        df.write("".join(pl))
        df.close()
        
    def handle_class(self, tokens):
        result = []
        
        self.in_class = True
        
        cls = tokens[0]
        full_cls = cls
        if self.current_ns:
            full_cls = "%s.%s" % (self.current_ns, cls)
            cls = "%s.%s" % (self.current_alias, cls)
            
        try:
            super_cls = tokens[2]
        except IndexError:
            super_cls = "Ext.util.Observable"
        
        # Find the namespace portion of the class and insert a namespace 
        # declaration if it hasn't already been declared
        nsi = full_cls.rfind('.') 
        if nsi > 0:
            ns = full_cls[0:nsi]
            if not ns in self.namespaces:
                self.namespaces.append(ns)
                result.append(self.get_namespace_str(ns))
        
        result.append("(function () {\n")
        result.append("var supercon = %s;\n" % super_cls)
        result.append("var superproto = supercon.prototype;\n")
        result.append("var con = %s = Ext.extend(supercon, {\n" % cls)
        
        return "".join(result)
        
    def handle_cls(self):
        return "});\nExt.apply(con, {\n"

    def handle_endclass(self, tokens):
        self.in_class = False
        return "});\n})();\n"
        
    def handle_ns(self, tokens):
        result = []
        
        ns = tokens[0]
        try:
            alias = tokens[2]
        except IndexError:
            alias = "ns"
            
        self.current_ns = ns
        self.current_alias = alias
        
        if not ns in self.namespaces:
            self.namespaces.append(ns)
            result.append(self.get_namespace_str(ns))
            
        result.append("(function () {\n")
        result.append("var %s = %s;\n" % (alias, ns))
        
        return "".join(result)
        
    def handle_endns(self, tokens):
        self.current_ns = None
        self.current_alias = None
        
        return "})();\n"
        
    def get_namespace_str(self, ns):
        return "Ext.namespace('%s');\n" % ns
        
    def handle_waitfor(self, tokens):
        try:
            name_end = tokens.index(":")
            name = "".join(tokens[:name_end])
        except ValueError:
            name_end = -1
            self.callback_count = self.callback_count + 1
            name = "generatedCallback%d" % self.callback_count
            
        try:
            argument_end = tokens.index("=")
        except ValueError:
            # There is no point in continuing
            return ""
            
        arguments = " ".join(tokens[name_end + 1:argument_end])
        call = " ".join(tokens[argument_end + 1:])
        
        result = []
        
        if self.in_class:
            call = call.replace("**", "this.%s.createDelegate(this)" % name)
            result.append("\t\t%s\n" % call)
            result.append("\t},\n\t\n")
            result.append("\t%s: function (%s) {\n" % (name, arguments))
            
        else:
            call = call.replace("**", name)
            result.append("\t%s" % call)
            result.append("}\n\n")
            result.append("function %s(%s) {" % (name, arguments))
            
        return "".join(result).expandtabs(4)
    
def usage():
    print """\
Usage: 
 jsprocessor <source> [<destination>]
  
This script reads a JavaScript file and processes any directives that are 
specified in the file.

If <destination> is not present, the <source> file will be overwritten with 
any changes made by the processor.
"""

def main():
    try:
        opts, args = getopt.getopt(
                sys.argv[1:], "", [])
    except getopt.GetoptError:
        usage()
        sys.exit(2)
    
    path = os.getcwd()
    if len(args) == 1:
        source = args[0]
        destination = args[0]
    elif len(args) == 2:
        source = args[0]
        destination = args[1]
    else:
        usage()
        sys.exit(2)
    
    print "jsprocessor starting..."
    
    if not os.path.isfile(source):
        source = os.path.join(path, source)
        
    if not os.path.isfile(destination):
        destination = os.path.join(path, destination)
    
    p = JSProcessor(source, destination)
    p.process()
        
    print "Done"

if __name__ == "__main__":
    main()
    
